package boot;

import audio.WeaponSound;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.BloomFilter;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.shadow.BasicShadowRenderer;
import com.jme3.terrain.geomipmap.TerrainQuad;
import controls.PlayerControls;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import world.AreaManager;
import physics.Lighting;
import player.PlayerMovement;
import com.jme3.renderer.RenderManager;
import world.SkyManager;

/**
 * Example 9 - How to make walls and floors solid.
 * This version uses Physics and a custom Action Listener.
 * @author normen, with edits by Zathras
 */
public class MainBoot extends SimpleApplication
        implements ActionListener {

    private Spatial sceneModel;
    private BulletAppState bulletAppState;
    private RigidBodyControl landscape;
    //private CharacterControl player;
    private Vector3f walkDirection = new Vector3f();
    private boolean left = false, right = false, up = false, down = false;
    private AreaManager areaManager = new AreaManager();
    private Lighting lights = new Lighting();
    PlayerMovement playerMoves;
    PlayerControls PC = new PlayerControls();
    SkyManager sky = new SkyManager();
    WeaponSound weaponSound = new WeaponSound();
    public RigidBodyControl terrainPhysicsNode;
    public TerrainQuad terrain;
    //Materials
    public Material matRock;
    public HashMap<String, Node> modelList;
    public Node model;
    BasicShadowRenderer bsr;
    //for shooting
    Node shootables;
    Geometry box;

    public static void main(String[] args) {
        MainBoot app = new MainBoot();
        app.start();
    }

    public MainBoot() {
        ref = this;
    }

    public static synchronized MainBoot getMain() {
        return ref;
    }

    public void simpleInitApp() {
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        //viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
        //flyCam.setMoveSpeed(100);
        setUpKeys();
        lights.setUpLight();


        playerMoves = new PlayerMovement();
        initCrossHairs();


        sky.createSky();
        initShadows();
        setupFilter();


        //loads the town. floor glitches present
        createBasicScene();

        //loads the countryside beside it
        areaManager.createTerrain();
        weaponSound.initAudio();
        initShootingObjects();

        playerMoves.spawnCountry();
        bulletAppState.getPhysicsSpace().add(playerMoves.Control());
    }

    @Override
    public void simpleUpdate(float tpf) {
        Vector3f camDir = cam.getDirection().clone().multLocal(playerMoves.playerSpeed);
        Vector3f camLeft = cam.getLeft().clone().multLocal(playerMoves.playerSpeed);
        walkDirection.set(0, 0, 0);
        if (left) {
            walkDirection.addLocal(camLeft);
        }
        if (right) {
            walkDirection.addLocal(camLeft.negate());
        }
        if (up) {
            walkDirection.addLocal(camDir);
            System.out.println(camDir);

        }
        if (down) {
            walkDirection.addLocal(camDir.negate());
        }
        //sets camera to be the players view

        playerMoves.Control().setWalkDirection(walkDirection);
        cam.setLocation(playerMoves.Control().getPhysicsLocation());
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }

    protected void initCrossHairs() {
        guiNode.detachAllChildren();
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText ch = new BitmapText(guiFont, false);
        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
        ch.setText("+"); // crosshairs
        ch.setLocalTranslation( // center
                settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
        guiNode.attachChild(ch);
    }

    //SETUP GAME FUNCTIONS AFTER THIS POINT. ABOVE WAS FUNDAMENTAL UPDATES FOR GAME
    //sets up keyboard mapping for controls
    private void setUpKeys() {

        HashMap<String, KeyTrigger> ctrls = PC.PlayingKeys();

        Iterator it = ctrls.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry ctrl = (Map.Entry) it.next();

            inputManager.addMapping((String) ctrl.getKey(), (KeyTrigger) ctrl.getValue());
            inputManager.addListener(this, (String) ctrl.getKey());
        }
        inputManager.addMapping("Shoot", new MouseButtonTrigger(0));
        inputManager.addListener(this, "Shoot");

    }

    public void onAction(String binding, boolean value, float tpf) {
        if (binding.equals("Lefts")) {
            left = value;
        } else if (binding.equals("Rights")) {
            right = value;
        } else if (binding.equals("Ups")) {
            up = value;
        } else if (binding.equals("Downs")) {
            down = value;
        } else if (binding.equals("Jumps")) {
            playerMoves.Control().jump();
        } else if (binding.equals("Sprints")) {
            playerMoves.toggleSprint();
        } else if (binding.equals("Crouches")) {
            System.out.println("Crouches");
        } else if (binding.equals("Shoot")) {
            pockMark();
            weaponSound.playWeapon("pistol");
        }
    }

    //all shading on everything for the moment. Maybe implement another
    //singleton with this to handle all shading. right now too costly
    private void initShadows() {
        bsr = new BasicShadowRenderer(assetManager, 256);
        bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
        viewPort.addProcessor(bsr);
        // Default mode is Off -- Every node declares own shadow mode!
        rootNode.setShadowMode(ShadowMode.CastAndReceive);
    }

    private void setupFilter() {
        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
        BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects);
        fpp.addFilter(bloom);
        viewPort.addProcessor(fpp);
    }

    private void createBasicScene() {
        sceneModel = assetManager.loadModel(areaManager.loadArea(0));
        //sceneModel.setLocalScale(2f);
        sceneModel.setLocalScale(new Vector3f(1, 1, 1));
        CollisionShape sceneShape = CollisionShapeFactory.createMeshShape((Node) sceneModel);
        landscape = new RigidBodyControl(sceneShape, 0);
        sceneModel.addControl(landscape);
        rootNode.attachChild(sceneModel);
        bulletAppState.getPhysicsSpace().add(landscape);
    }

    //hashList of enemies : WORK IN PROGRESS
    private void initShootingObjects() {

        modelList = new HashMap<String, Node>();
        model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
        modelList.put("enemy", model);
        model.setLocalScale(6f);
        model.setLocalTranslation(new Vector3f(-140, 10, -10));
        //bulletAppState.getPhysicsSpace().add(model);
        shootables = new Node("Shootables");
        rootNode.attachChild(shootables);
        shootables.attachChild(model);

    }

    //reimplemented to get collision detection in order. used to paint holes. too much graphics
    protected void pockMark() {
        // 1. Reset results list.
        CollisionResults results = new CollisionResults();
        // 2. Aim the ray from cam loc to cam direction.
        Ray ray = new Ray(cam.getLocation(), cam.getDirection());
        // 3. Collect intersections between Ray and Shootables in results list.
        shootables.collideWith(ray, results);
        // 4. Print the results.
        System.out.println("----- Collisions? " + results.size() + "-----");
        for (int i = 0; i < results.size(); i++) {
            // For each hit, we know distance, impact point, name of geometry.
            float dist = results.getCollision(i).getDistance();
            Vector3f pt = results.getCollision(i).getContactPoint();
            String hit = results.getCollision(i).getGeometry().getName();

            System.out.println("* Collision #" + i);
            System.out.println("  You shot " + hit + " at " + pt + ", " + dist + " wu away.");

        }
    }

    public synchronized BulletAppState getBulletAppState() {
        return bulletAppState;
    }
    private static MainBoot ref = null;
}//end of class